package com.lpb.spring.lpbspring.getway;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.http.ContentType;
import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.ByteString;

import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author haopeng
 */
//@Slf4j
public class OpenGwHttpUtil {

    //"open-api-qapub.qa.tongdao.cn";
    private static final String OPEN_API_DOMAIN="open-api-qapub.qa.tongdao.cn";



    private static final String APP_KEY ="69cb9f273cd3fbe9c82b7d925b5a2b3f";
    private static final String APP_SECRET ="wq0sGE7aKTuautwkDL8vsg";

    /**
     * MIME: form
     */
    private static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
    /**
     * MIME: Json
     */
    private static final String APPLICATION_JSON_VALUE = "application/json; charset=utf-8";

    private volatile static OpenGwHttpUtil httpUtils;
    private final OkHttpClient okHttpClient;

    private OpenGwHttpUtil() {
        ConnectionPool connectionPool = new ConnectionPool(30, 10, TimeUnit.MINUTES);
        okHttpClient = new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).connectionPool(connectionPool).retryOnConnectionFailure(false).build();
    }

    public static OpenGwHttpUtil getInstance() {
        if (httpUtils == null) {
            synchronized (OpenGwHttpUtil.class) {
                if (httpUtils == null) {
                    httpUtils = new OpenGwHttpUtil();
                }
            }
        }
        return httpUtils;
    }

    /**
     * 执行POST方法
     *
     * @param path       请求地址
     * @param formParams 表单信息
     * @return 查询结果
     * @throws Exception 异常处理
     */
    public String post(String path, Map<String, String> formParams) throws Exception {
        String uri = "http://" + OPEN_API_DOMAIN + path;
        uri = URLUtil.normalize(uri);
        System.out.println("OPEN API请求接口地址：" + uri);
        // 生成 Headers
        Map<String, String> headers = generateHeaders(path, formParams, Method.POST.name());
        // 构建 FormBody
        FormBody formBody = buildFormBody(formParams);

        // 构建 Request
        Request request = new Request.Builder().url(uri).headers(Headers.of(headers)).post(formBody).build();

        //request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        // 发起 Request，读取 Response Body
        String responseBody = executeRequestInternal(request);
        if (StrUtil.isBlank(responseBody)) {
            throw new Exception("请求OpenApi接口响应异常！");
        }
        Map<String, String> logParams = new LinkedHashMap<>(formParams);
        logParams.keySet().forEach(key -> {
            if (key.startsWith("image")) {
                logParams.put(key, "<image>");
            }
        });
        System.out.println(String.format("OpenApi接口 Request %s %s Response:%s params:%s", OPEN_API_DOMAIN, path, responseBody, JSONUtil.toJsonStr(logParams)));
        return responseBody;
    }

    private RequestBody buildJsonObj(Map<String, Object> formParams) {
        String s = JSON.toJSONString(formParams);
        // RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), s);
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), s);
    }

    /**
     * Json 请求
     *
     * @param path
     * @param formParams
     * @return
     * @throws Exception
     */
    public String postJson(String path, Map<String, Object> formParams) throws Exception {
        String uri = "http://" + OPEN_API_DOMAIN + path;
        uri = URLUtil.normalize(uri);
        System.out.println("OPEN API请求接口地址：" + uri);
        // 生成 Headers
        Map<String, String> headers = generateJsonHeaders(path, formParams);
        // 构建 json
        RequestBody requestBody = buildJsonObj(formParams);
        // 构建 Request
        Request request = new Request.Builder().url(uri).headers(Headers.of(headers)).post(requestBody).build();
        // 发起 Request，读取 Response Body
        String responseBody = executeRequestInternal(request);
        if (StrUtil.isBlank(responseBody)) {
            throw new Exception("请求OpenApi接口响应异常！");
        }
        Map<String, Object> logParams = new LinkedHashMap<>(formParams);
        logParams.keySet().forEach(key -> {
            if (key.startsWith("image")) {
                logParams.put(key, "<image>");
            }
        });
        System.out.println(String.format("OpenApi接口 Request %s %s Response:%s params:%s", OPEN_API_DOMAIN, path, responseBody, JSONUtil.toJsonStr(logParams)));
        return responseBody;
    }

    private Map<String, String> generateJsonHeaders(String path, Map<String, Object> formParams) throws Exception {
        // 请求头
        Map<String, String> headers = new HashMap<>();
        headers.put(GenAppSignatureBean.X_ALT_TIMESTAMP, String.valueOf(Calendar.getInstance().getTimeInMillis()));
        headers.put("accept", APPLICATION_JSON_VALUE);

        // 生成鉴权事，必须要有头信息 x-alt-timestamp
        GenAppSignatureBean.AppAuthBean appAuthBean = getAppAuthBeanForJson(headers, path, formParams);
        headers.put("Authorization", appAuthBean.getAuthorizationHeaderValue());
        if (StrUtil.isNotBlank(appAuthBean.getContentMd5())) {
            headers.put("Content-MD5", appAuthBean.getContentMd5());
        }

        return headers;
    }

    private GenAppSignatureBean.AppAuthBean getAppAuthBeanForJson(Map<String, String> headers, String path, Map<String, Object> formParams) throws Exception {
        if (!ObjectUtil.isAllNotEmpty(OPEN_API_DOMAIN, APP_KEY, APP_SECRET)) {
            throw new Exception("配置错误：OPEN_API_DOMAIN=" + OPEN_API_DOMAIN + ";APP_KEY=" + APP_KEY + ";APP_SECRET=" + APP_SECRET);
        }
        String s = JSON.toJSONString(formParams);
        return GenAppSignatureBean.builder().appKey(APP_KEY).appSecret(APP_SECRET).algorithm(GenAppSignatureBean.ALGORITHM_HMACSHA256).signHeaders(headers).method("POST").contentType(APPLICATION_JSON_VALUE).formData(false).accept(APPLICATION_JSON_VALUE).path(path).body(s.getBytes("utf-8")).build().createAppAuth();
    }

    /**
     * 执行get方法
     *
     * @param path   请求地址
     * @param params URL参数
     * @return 查询结果
     * @throws Exception 异常处理
     */
    public String get(String path, Map<String, String> params,ContentType contentType) throws Exception {
        String uri = "http://" + OPEN_API_DOMAIN + path;
        uri = URLUtil.normalize(uri);
        System.out.println("OPEN API请求接口地址：" + uri);
        // 生成 Headers
        Map<String, String> headers = generateHeaders(path, params, Method.GET.name());
        UrlBuilder ub = UrlBuilder.of(uri);
        params.forEach(ub::addQuery);
        uri = ub.build();
        // 构建 Request
        Request request = new Request.Builder().url(uri).headers(Headers.of(headers)).get().build();
        // 发起 Request，读取 Response Body
        String responseBody = executeRequestInternal(request);
        if (StrUtil.isBlank(responseBody)) {
            throw new Exception("请求OpenApi接口响应异常！");
        }
        System.out.println(String.format("OpenApi接口 Request %s %s Response:%s params:%s", OPEN_API_DOMAIN, path, responseBody, JSONUtil.toJsonStr(params)));

        return responseBody;
    }

    private Map<String, String> generateHeaders(String path, Map<String, String> formParams, String method) throws Exception {

        // 请求头
        Map<String, String> headers = new HashMap<>();
        headers.put(GenAppSignatureBean.X_ALT_TIMESTAMP, String.valueOf(Calendar.getInstance().getTimeInMillis()));
        headers.put("accept", APPLICATION_JSON_VALUE);
        // 生成鉴权事，必须要有头信息 x-alt-timestamp
        GenAppSignatureBean.AppAuthBean appAuthBean = getAppAuthBean(headers, path, formParams, method);
        headers.put("Authorization", appAuthBean.getAuthorizationHeaderValue());
        if (StrUtil.isNotBlank(appAuthBean.getContentMd5())) {
            headers.put("Content-MD5", appAuthBean.getContentMd5());
        }
        return headers;
    }

    private GenAppSignatureBean.AppAuthBean getAppAuthBean(Map<String, String> signHeaders, String path, Map<String, String> formParams, String method) throws Exception {
      return GenAppSignatureBean.builder().appKey(APP_KEY).appSecret(APP_SECRET).algorithm(HmacAlgorithm.HmacSHA256.getValue()).signHeaders(signHeaders).method(method).contentType(ContentType.FORM_URLENCODED.getValue()).formData(true).accept(APPLICATION_JSON_VALUE).path(path).formParams(formParams).build().createAppAuth();
    }

    private FormBody buildFormBody(Map<String, String> formParams) {
        FormBody.Builder builder = new FormBody.Builder();
//        if(contentType!=null){
//            builder.add("")
//        }
        if (CollectionUtil.isNotEmpty(formParams)) {
            for (Map.Entry<String, String> entry : formParams.entrySet()) {
                builder.add(entry.getKey(), entry.getValue());
            }
        }
        return builder.build();
    }

    /**
     * 发出http post请求。
     *
     * @param request http request。
     * @return response body。
     */
    private String executeRequestInternal(Request request) throws Exception {
        // 发起http request
        try (Response response = OpenGwHttpUtil.getInstance().execute(request)) {
            if (!response.isSuccessful()) {
                throw new Exception(response.toString());
            }
            // 读取并返回response body
            assert response.body() != null;
            return response.body().string();
        } catch (IOException e) {
            throw new Exception(e.getMessage());
        }
    }

    private Response execute(Request request) throws IOException {
        return okHttpClient.newCall(request).execute();
    }

}
